06. 캐시 최적화

📝 Contents

캐시

  • 콘텐츠 요청에 빠르게 응답하기 위해 서버와 클라이언트 사이에서 응답 콘텐츠의 사본을 저장하는 공간을 캐시라고 함
  • 이 캐시를 유지하고 처리해주는 별도의 서버를 캐시 서버라고 함
  • 캐시는 보통 클라이언트와 서버 사이에 존재함
  • 캐시의 종류에는 브라우저 캐시, 프록시 캐시, 트랜스패어런트 캐시, 리버스 프록시 캐시가 있음
  • 이 절에서 주로 설명할 캐시 서버는 웹 캐시라 불리는 리버스 프록시 캐시임
  • 웹 서버인 Apache HTTP server에서 모듈들을 사용해 캐시 서버로 활용할 수 있고, Nginx에도 콘텐츠 캐시 기능을 제공함
  • 이 외에도 오픈 소스 웹 캐시 서버들이 있음
    • Apache Traffic Server
    • Nginx(캐시 기능을 제공하는 웹 서버)
    • Varnish Cache

웹 캐시 동작 원리

  • 캐시 서버는 HTTP/1.1 규격(RFC2616)을 기반으로 동작하므로 HTTP에 대한 기본 지식을 먼저 이해해야 함

HTTP

  • 인터넷에서 데이터를 주고받기 위한 클라이언트/서버 모델을 따르는 프로토콜
  • OSI 7계층 모델의 마지막 7계층인 Application 레벨의 프로토콜이며 TCP/IP 위에서 동작함
  • 일단 TCP Connection이 맺어지면 HTTP는 어떤 종류의 데이터든 전송할 수 있도록 설계됨
  • HTTP 메시지는 크게 헤더와 페이로드로 구분됨
  • 헤더에는 메시지를 전송할 호스트명, URL 패스 등 메시지 전송 및 처리에 필요한 데이터들이 포함되어 있음
  • 페이로드는 html, 이미지 등 서버가 실제 전송하고자 하는 데이터를 포함함

HTTP의 캐시 제어 방식

  • HTTP/1.0까지는 캐시를 제어하는 명시적인 기술이 없고, 웹 콘텐츠가 있는 원본 서버 자원들이 언제까지 유효한지 그리고 만료일 이후 해당 자원이 실제 변경되었는지를 확인하는 요청/응답 헤더를 정의하고 이에 대해서만 기록함
  • HTTP/1.1부터 명시적으로 캐시를 제어할 수 있는 Cache-Control 헤더를 추가함
  • HTTP/1.1에서는 캐시를 제어하는 목적을 크게 두 가지로 정의함
    • 원본 서버로의 요청 수를 최소화합니다. 이는 네트워크 왕복 수를 줄여 결과적으로 사용자 요청에 대한 응답 속도를 단축할 수 있습니다.
    • 완전한 콘텐츠를 응답하지 않아도 됩니다. 이는 네트워크 대역폭과 리소스 낭비를 줄이고 비용을 효율화합니다.
Expire
  • HTTP/1.0에서는 Expire 헤더를 사용해 원본 서버 콘텐츠 유효 기간을 지정하도록 정의함
  • Expire 헤더는 캐시를 명시적으로 제어하지는 않지만 브라우저를 포함한 대부분 캐시 서버에서 콘텐츠를 언제까지 저장할 것인지 판단하기 위해 사용함
Cache-Control: max-age
  • HTTP/1.1에서는 Cache-Control: max-age라는 헤더로 콘텐츠의 캐시 유지 시간을 정의함
  • Expire 헤더는 만료 일자를 지정하는 반면 Cache-control: max-age는 유효 기간을 지정함
  • 두 헤더는 '원본 서버 콘텐츠의 캐시 TTL을 결정'하는 동일한 용도로 사용됨
Cache-Control: s-maxage
  • CDN과 같은 공용 캐시 주기를 관리함
  • 일반적으로 대부분 CDN 업체는 캐시 주기를 설정하고 관리하는 별도 방법을 사용해서 다수의 CDN 캐시를 동시에 사용하면 이들을 통합 관리하기 쉽지 않음
  • s-maxage를 이용하면 사용중인 모든 CDN의 캐시 주기를 일괄적으로 설정하거나 변경할 수 있음
  • s-maxage는 표준으로 도입된 지 얼마 되지 않았으므로 CDN업체가 이 헤더를 지원하는지 먼저 확인해야 함
ETag
  • 원본 서버가 리소스를 식별하기 위해 부여하는 고유 번호
  • 캐시 서버에서는 ETag를 사용해 원본 서버의 리소스가 시간이 지나 만료되었는지, 캐시된 리소스를 새로 갱신해야 하는지 여부를 명확히 판단할 수 있음
  • ETag 값은 크기 'Strong' ETag와 'Weak' Etag 두 가지로 구분함
  • Strong ETag는 모든 리소스에 대해 유일한 값을 가져야 하므로 Weak Etagdp 비해 값을 생성하는 과정이 까다로움
  • Weak ETag를 사용하면 비교적 간단하게 값을 생성할 수 있지만 ETag 값에 대한 신뢰도가 약해짐
  • 이 때 ETag 값에 'w/'를 붙여 '이 값이 유일하지 않을 수 있지만 괜찮다'는 메시지를 캐시 서버에 전달해야 함
Cache-Control: public
  • public으로 설정하면 그 응답은 모든 캐시 서버에 캐시될 수 있고 사용자 제한 없이 모든 사용자에게 응답이 전달될 수 있음
Cache-Control: private
  • private으로 설정하면 HTTP 요청에 대한 응답은 요청한 사용자만 캐시할 수 있고 CDN 같은 범용 캐시 서버에서는 캐시할 수 없음
  • 엄밀하게 말하면 범용 캐시 서버에서도 캐시할 수 있지만 그 응답을 모든 사용자에게 공유할 수 없음
  • 결론적으로 최종 사용자의 브라우저에서만 이 응답을 자유롭게 캐시할 수 있음
  • Cache-Control: private을 설정한다고 해서 응답에 담긴 개인 정보까지 보호되지는 않으니 주의해야 함
Cache-Control: no-cache
  • no-cache 지시자는 요청과 응답 헤더에 모두 사용할 수 있지만 약간의 의미 차이가 있음
  • 이 지시자가 요청 헤더에 있으면 브라우저는 원본 서버나 그 중간에 존재하는 캐시 서버들에게 '캐시된 응답을 받지 않겠다'는 메시지를 전달하는 것과 같음
    • max-age=0을 사용하는 것과 비슷하지만 max-age=0은 캐시된 콘텐츠에 변경이 있는지를 먼저 검증함
  • 이 지시자가 HTTP 응답 해더에 포함되면 원본 서버가 캐시 서버들에게 캐시된 응답을 보내기 전 원본 서버를 항상 확인하도록 강제함
    • max-age=0 설정과 동일하게 동작함
Cache-Control: no-store
  • HTTP 요청 또는 응답 헤더에 모두 사용할 수 있고 쓰임새도 no-cache와 동일함
  • 이 지시문은 서버가 로컬 저장소에 메시지를 저장하지 않도록 지시함
  • no-cache는 응답을 항상 최신 상태로 유지하도록 지시하지만 로컬 저장소에 저장하는 것을 막지 않음
  • 반면 no-store는 응답 메시지가 저장소에 저장되는 것 자체를 금지함
  • 결국 이 지시문의 목적은 캐시 데이터의 예기치 않은 유출을 방지하려는 것임

캐시 유효성 체크

  • 캐시에 저장되어 있는 응답의 age가 max-age값을 넘었다면 캐시는 원본 서버에 요청을 보내 새 응답을 받아야 함
  • 이 때 해당 콘텐츠에 아무런 변화가 없었다면 이미 캐시된 내용과 동일한 응답을 다시 만들어 보낼 것이므로 원본 서버에서는 의미 없는 응답을 만들어 전송하기 위해 서버 자원과 네트워크 대역폭을 낭비함
  • 이를 방지하고자 HTTP 표준은 조건부 요청이라는 매커니즘을 정의함
  • 이 매커니즘은 저장된 응답 TTL이 만료되었을 경우 캐시가 항상 원본 서버에서 완전한 콘텐츠를 받아오는 대신 TTL 주기 동안 콘텐츠에 변화가 있을 때에만 새 응답을 만들도록 요청함
  • 조건부 요청을 보낼 때는 시간을 기반으로 보내는 방법과 콘텐츠를 기반으로 보내는 두 가지 방법을 사용할 수 있음
    • 시간 기반의 조건부 요청은 콘텐츠의 최종 변경 시간 중심으로 확인(If-Modified-Since)
    • 콘텐츠 기반의 조건부 요청은 콘텐츠 고윳값 중심으로 확인(If-None-Match)
      • 콘텐츠 고윳값은 주로 해시값으로 추출하고 ETag에 이 고윳값을 넣어 확인

캐시 콘텐츠 갱신

  • 웹 사이트가 개편되었거나 콘텐츠를 급하게 변경했다면 캐시에 저장된 복사본들을 강제로 갱신해야 사용자에게 정상적인 웹 페이지를 서비스할 수 있음
  • 캐시에 저장된 내용을 갱신하기 위해 두 가지 방법을 사용할 수 있음
퍼지
  • 저장소를 완전히 지우는 방식으로 대부분의 캐시 서버가 캐시를 모두 지우라는 명령어나 API를 제공함
  • 브라우저 옵션 메뉴에서 로컬 캐시를 지울 수 있음
  • 캐시 서버에서 한꺼번에 많은 콘텐츠를 퍼지하려면 원본 서버에 충분한 자원이 있는지 확인하는 등 주의를 기울여야 함
  • 캐시되지 않은 많은 요청이 한꺼번에 원본 서버로 몰려 서버 리소스가 많이 사용도는 부담이 있기 때문임
무효화
  • 조건부 요청을 통해 캐시된 리소스들 중 변경이 있었던 리소스들만 새로 갱신하는 방법
  • 퍼지와 동일하게 새 콘텐츠를 받아가려는 트래픽이 잠시 증가할 수 있지만, 실제로 변경된 리소스 한에서만 전체 콘텐츠가 반환되므로 네트워크 대역폭 낭비를 크게 줄일 수 있음

캐시 최적화 방안

  • 캐시 사용을 최대화할 수 있는 3가지 기본 원리는 다음과 같음

  • 최대한 많이 캐시하라

  • 최대한 오래 캐시하라
  • 최대한 가까이 캐시하라

캐시 가능한 콘텐츠 구분하기

  • 먼저 웹 페이지의 어떤 콘텐츠를 캐시할 수 있는지, 혹은 캐시하면 안 되는지 바로 알아야 함
  • 캐시할 수 있는 리소스들을 최대한 찾아내 캐시를 통해 서비스해야 함
  • 웹 사이트를 구성하는 콘텐츠는 크게 정적인 콘텐츠와 동적인 콘텐츠로 분류할 수 있음
  • 정적 콘텐츠란 URL을 호출할 떄마다 변함없이 같은 응답을 주는 콘텐츠로 이미지, CSS, 자바스크립트 등이 대표적인 정적 콘첸츠에 속함
    • 이들은 페이지를 동적이고 화려하게 만드는데 중요한 역할을 하지만 호출할 때마다 소스 코드 자체가 달라지진 않기 때문에 정적 콘텐츠로 분류함
  • 동적 콘텐츠란 사용자가 요청할 때마다 서버에 의해 다시 생성, 응답되는 콘텐츠로 서버에서는 HTTP 요청과 함께 입력 시간을 포함한 다양한 변숫값에 따라 콘텐츠를 동적으로 생성하여 응답함
    • Ajax를 사용한 XHR 요청이나 개인화된 웹 페이지들이 대표적인 동적 콘텐츠임


  • 결론적으로, 캐시하기 어려운 콘텐츠는 아래와 같이 분류 할 수 있음
    • 개인화된 콘텐츠
    • API 호출이나, Ajax 요청에 대한 콘텐츠
    • Beacon 전달 또는 쿠키 설정을 위한 호출

올바른 캐시 정책 설정하기

  • 캐시 정책을 세우는 것은 캐시할 콘텐츠들의 성격을 파악하고 그룹화하는 것과 같음
  • 우선 웹 사이트를 구성하는 리소스들을 같은 타입끼리 그룹화하고 아래와 같은 순서를 참고해 하위 그룹을 나누고 캐시 정책을 정의함

  • 먼저 캐시할 수 있는 콘텐츠인지 판단합니다.

    • 일반적으로 정적 콘텐츠들을 캐시 가능한 페이지로 정의할 수 있지만, 보안에 민감한 내용이 담겨 있는 정적 콘텐츠는 캐시하면 안됨
    • 동적 콘텐츠라고 모두 캐시가 불가능 하지 않음
    • 캐시할 수 없는 콘텐츠에는 응답헤더에 Cache-Control: no-store를 붙임
  • 캐시할 수 있는
    • 변경에 민감한 리소스는 응답 헤더에 Cache-Control: no-cache나 Cache-Control: max-age=0을 사용해 설정함
  • 캐시할 콘텐츠의 성격을 판단합니다.
    • 모든 사용자에게 공통으로 사용될 수 있다면 Cache-Control: public을, 개인화된 콘텐츠라면 Cache-Control: private을 사용함
    • private을 설정한 경우 일반적인 웹 캐시 서버에서 해당 콘텐츠를 캐시할 수 없고 오직 개인 브라우저에만 캐시함
  • 마지막으로 캐시 주기를 설정하고 max-age를 추가합니다.

캐시 주기 결정하기

  • 기본적인 캐시 정책이 결정되면 구체적으로 얼마 동안 캐시할 것인지 결정해야 함

  • 캐시 주기는 콘텐츠 타입별로 다르게 설정할 수 있습니다.

    • 이미지나 동영상 등 미디어 파일은 한번 창작되어 웹 페이지에 게시되면 쉽게 변경되지 않음
    • 변경해야 할 때 링크를 변경하는 경우가 많음
    • 링크 자체가 바뀌면 자연스럽게 캐시 서버가 원본 서버로부터 해당 콘텐츠를 불러와 캐시함
    • 따라서 특별한 이유가 없다면 캐시 주기를 1년 정도로 길게 설정하는 것을 권장함
  • 만약 링크 변경 없이 이미지 내용만 바꿔야 한다면 캐시 무효화 방식으로 해당 이미지만 캐시에 업데이트합니다.
  • 모든 정적 파일에 대해 캐시 주기를 길게 설정하고 수동으로 캐시 주기를 관리하는 방법도 있습니다.

캐시에 적합한 디렉터리 구조 구성하기

  • 웹 콘텐츠의 구성 요소들을 파악하고 콘텐츠별 캐시 정책을 정의했다면 캐시 친화적 디렉터리 구조를 구성하는 것을 권함
  • 캐시에 적합한 디렉터리 구조를 구성하려면
  • 첫 번째로 캐시할 수 있는 콘텐츠들을 별도에 폴더에 분류해 관리하기
  • 두 번째로 캐시 주기별로 나누어 구성하기
  • 세 번째로 동일한 파일을 여러 곳에 분산시키지 않기

캐시 키 올바르게 사용하기

  • 캐시 키란 캐시 서버가 원본의 복사본을 저장하고 빠르게 조회하기 위해 사용하는 키 값을 말함
  • 일반적으로 웹 캐시는 클라이언트가 요청하는 URL을 캐시 키로 사용함


  • 원본 서버에 하나의 원본 파일만 존재하는데 캐시에 복사본이 여러 개 존재하는 것을 캐시 오염이라고 함
  • 캐시 오염은 최종 사용자에게 영향을 주지는 않지만 캐시 서버의 효율성에 큰 영향을 미칠 수 있음
  • 또한 캐시가 퍼지된 경우 원본 서버가 예기치 않은 트래픽 부담을 줄 수 있음
  • 캐시 오염을 피하기 위해서
  • 첫 번쨰로 URL에 붙은 특정 쿼리 스트링 값이 달라지더라도 응답이 항상 같다면 캐시 키에서 쿼리 스트링을 무시하도록 설정해야 함
  • 두 번재로 쿼리 스트링의 순서를 동일하게 정렬해야 함
    • 쿼리 스트링의 순서가 바뀌면 값은 동일한데, 캐시에서는 각각의 URL들을 다르게 인식하고 다른 캐시 키로 저장함
  • 세 번째로 Vary 헤더를 바르게 사용해야 함


  • 캐시 충돌이란 요청 URL이 하나인데 브라우저 환경에 따라 서버에서 제공하는 응답이 달라져 결국 최초 요청한 브라우저의 응답만 캐시되는 것을 의미함
    • 이 경우 나머지 브라우저 환경을 사용하는 사용자들은 잘못된 응답을 받음
  • 캐시 충돌은 동적 페이지를 캐시할 때 주로 발생함
  • 이 현상을 피하려면 기본적으로 동적 페이지에는 캐시를 적용하지 않아야 함
  • 일부 동적 페이지에 캐시를 사용하고자 한다면 Cache-Control: private으로 사용자 브라우저에만 캐시하여 페이지 로딩 시간을 단축할 수 있음

CDN 사용하기

  • 캐시 효율화를 위한 3원칙 중 마지막은 사용자에게 가깝게 캐시하라는 것임
  • CDN 서비스를 사용하면 세계 여러 지역 데이터 센터들에 리버스 프록시 캐시 서버를 두고 필요한 정적 콘텐츠들을 저장해놓을 수 있음
  • 또한 사용자가 관련 콘텐츠를 요청할 때 사용자와 가장 가까운 캐시 서버에서 해당 콘텐츠가 서비스되므로 시간 지연 없이 빠르게 웹 페이지를 로딩할 수 있음

동적 콘텐츠 캐시

  • 서버에서 동적 콘텐츠를 처리하는 시간이 전체 응답 시간 중 많은 부분을 차지함
  • 동적 콘텐츠를 캐시할 수 있다면, 정적 콘텐츠를 캐시하는 것보다 더 많은 성능 개선 효과를 얻음
  • 웹 콘텐츠들을 특성에 따라 분류해 보면, 캐시할 수 있는 동적 콘텐츠들을 찾을 수 있음

  • 정적 콘텐츠와 동적 콘텐츠

  • 익명 콘텐츠와 개인화 콘텐츠
  • 시간에 민감한 콘텐츠와 시간에 둔감한 콘텐츠

동적 콘텐츠 캐시

  • 동적 콘텐츠를 사용자에게 전달하기 위해 원본 서버는 두 가지 방법을 사용함

  • 동적 정보를 쿠키에 넣어 보낸다.

  • Ajax 요청으로 관련 정보를 동적으로 받아온다.

  • 쿠키, 헤더 혹은 쿼리 스트링에 동적 콘텐츠에 대한 정보가 있으면 이 정보들을 캐시 키에 추가함으로써 동적 콘텐츠를 캐시할 수 있음

  • 이때 첫 번째로 보안에 주의해야 함
    • 대부분의 개인 정보는 POST 요청으로 보내고 POST 요청에 대한 응답은 캐시하지 않는 것이 일반적이지만, 보안에 관한 문제는 여러 번 확인하고 조심해도 지나치지 않음
  • 두 번째로 캐시 서버 용량에 유의해야 함


  • Ajax 요청을 통해 전달되는 동적 콘텐츠를 캐시하는 방법은 간단함
    • Ajax 요청에 대한 응답 형태인 JSON/XML 타입 콘텐츠는 다른 정적 응답 타입과 동일한 방식으로 캐시할 수 있기 때문임
  • 단지 시간에 민감한지 여부가 관건임


  • 캐시는 통상 HTTP GET 방식에서 동작하므로 Ajax 요청에 대한 응답을 캐시하고자 하면 HTTP POST 방식보다 GET 방식을 사용함
  • 캐시 주기로 0 TTL을 사용하려면 서버에서 해당 콘텐츠에 대한 If-Modified-Since(IMS)(조건부 요청 요청을 지원해야 함
  • IMS 요청을 지원하지 않으면 캐시하지 않는 것과 동일함

POST 응답 캐시

  • POST 메서드는 HTTP 페이로드 메시지에 쿼리 스트링을 포함시켜 보낼 때 사용함
  • GET 메소드는 요청 URL에 붙여 보낼 수 있는 쿼리 스트링 길이에 제한이 있고, 요청 URL이 타인에게 쉽게 노출될 수 있으므로 개인 정보들을 GET 메소드를 이용해 서버에 보내는 것은 적합하지 않음
  • 반면 POST 메소드를 사용하면 HTTP 페이로드에 쿼리 스트링 내용을 포함해 보내므로 데이터 크기에 제한이 없고, 타인이 브라우저를 통해 쉽게 볼 수 없어 보안 측면에서도 상대적으로 안전함
  • 따라서 POST 메소드는 보통 브라우저 캐시나 조회 이력에 남지 않고 캐시 서버에 캐시되어서도 안됨


  • 만약 입력 매개 변수가 동일할 때 서버로부터 항상 동일한 응답이 반환된다면, 또 그 응답 내용이 보안 측면에서 공개되어도 안전한 내용이라면 POST 요청/응답 역시 캐시할 수 있음
    • 단 캐시 키에 요청 매개 변숫값들이 모두 포함되어야 캐시 오염, 캐시 충돌 같은 오류 현상을 방지할 수 있음
  • 또한 캐시 키에 매개 변숫값들이 노출되지 않아야 하므로 MD5 같은 해시 알고리즘을 이용해 타인이 쉽게 알 수 없도록 값을 암호화해야 함
  • POST 요청/응답을 캐시하려면 다음과 같은 조건을 만족해야 함
    • 매개 변숫값에 항상 같은 응답이 오는 경우
    • 개인 정보가 포함되지 않은 경우
    • 요청 사이즈가 크지 않은 경우
  • 요청 사이즈는 캐시 키를 구성하는 중요한 요소임
  • HTTP 페이로드와는 다르게 캐시 키는 하나의 스트링으로 구성되기 때문에 그 길이에 제한이 있을 수 있음

고급 캐시 전략

  • 웹 사이트의 90% 이상이 정적 콘첸츠인 것을 고려하면 캐시 서버의 성능이 웹 사이트 로딩 속도에 얼마나 큰 영향을 미치는지 짐작할 수 있음
  • 이 절에서는 캐시 서버에서 기본적으로 제공하는 캐시 기능 이외에 캐시 성능을 더욱 향상시키는 부가 기능들을 살펴봄

Edge Side Include

  • 첫 번째 HTML이 서버에서 브라우저까지 도달하는 시간(Time To First Byte)은 웹 사이트 성능을 측정하는 매우 중요한 지표임
  • 하지만 이 HTML을 구성하는 콘텐츠 중 서버에서 동적으로 만들어내는 부분들이 존재하기 때문에 많은 웹 관리자들이 첫 HTML을 캐시하지 않음
  • 동적으로 변하는 부분이 매우 작다면, 그 부분만 따로 뗴어 별도로 수행시킨 후 캐시된 나머지 부분과 다시 조합할 수 있다면 로딩 성능이 개선될 수 있음
  • 이처럼 인터넷 엣지에서 웹 페이지 조각을 동적으로 조합, 조립, 전달할 수 있도록 이에 대한 문법과 용도 등을 정의한 XML 기반 표준 마크업 언어가 ESI임


  • ESI를 사용하면 한 페이지 안에 다른 페이지를 포함시킬 수 있을 뿐만 아니라 각 페이지들이 독립 객체로 취급되어 각각 다른 캐시 정책을 사용할 수 있음
  • HTTP 헤더 정보를 변수로 참조할 수 있고, 조건문을 사용해 이 변숫값에 따라 다른 비즈니스 로직을 적용할 수도 있음
  • 에러 발생 시 이에 따른 예외 처리를 할 수도 있음
  • ESI를 사용하려면 먼저 사용 중인 웹 캐시가 ESI 언어를 지원해야 함
  • 사용 방식은 JSP나 ASP와 비슷한데 ESI 역시 일종의 프로그램 언어로 HTML 문서 내에서 프로그램 로직을 구현함
  • ESI에서 사용되는 대표적인 태그는 다음과 같음
    • <include> 태그: 현재 페이지의 현재 위치에 포함시키고자 하는 리소스를 명시함
    • <choose>/<when>/<otherwise> 태그: 조건부 로직
    • <try>/<attempt>/<except> 태그: 예외 처리 기능을 제공함
    • <remove> 태그: 이 태그 안의 내용은 ESI 처리기가 제거하고 넘어가 최종 HTML에는 보이지 않음


  • ESI는 다음과 같이 다양한 경우에 사용할 수 있음
  • 첫째, 페이지 내에 일부 동적인 부분이 존재할 때 이 부분만 별도 페이지로 만들어 본래 페이지에 동적으로 삽입할 수 있음
    • 단 ESI는 Ajax처럼 서버를 주기적으로 호출할 수 없어 실시간 주식 시세처럼 주기적 업데이트가 필요한 용도에는 적합하지 않음
    • 과도한 연산이 필요한 페이지에도 적합하지 않음
  • 둘째, 서로 다른 성격의 콘텐츠를 각각의 캐시 정책을 사용해 캐시하고자 할 때 유용함
  • 셋째, 사용자 등급에 따라 콘텐츠를 제한하고자 할 때 유용함
    • 이 방식을 사용하려면 보안을 더욱 구체적으로 고려해야 함
    • 사용자별 보안 토큰을 발급하고 각 페이지 호출 전 이 토큰 검증 단계를 추가해 인가되지 않은 사용자가 유료 콘텐츠를 사용하는 사례를 방지 해야함
    • <try>/<attempt>/<except> 태그를 사용해 비즈니스 로직을 작성할 수 있음
  • 넷째, 웹 애플리케이션 개발에 협업이 필요할 떄 유용함

HTML5 로컬 스토리지

  • HTML5의 큰 장점 중 하나는 HTML5만의 다양한 기능을 API를 통해 사용할 수 있게 함으로써 웹 페이지 자체가 일종의 웹/모바일 애플리케이션으로 구동될 수 있다는 것임
  • HTML5의 주요 기능 중 웹 스토리지 API 기능을 주요하게 살펴봐야 함
  • 이 기능을 사용하면 사용자의 주요 정보들이나 웹 리소스들을 브라우저 로컬 저장소에 저장하여 재사용할 수 있음
  • 기존에는 쿠키를 통해서만 사용자 정보를 저장했지만, 최소 5MB 크기의 웹 스토리지 API를 통해 더 많은 데이터를 안전하게 저장할 수 있음
  • 스토리지는 세션이 살아 있는 동안에만 저장되는 세션 스토리지와 영원히 저장되는 로컬 스토리지가 있음
  • IndexedDB는 오브젝트 스토어로써 더 광범위한 데이터를 저장할 때 사용함
  • 쿠키는 매번 네트워크를 통해 전달되어 보안에 취약하지만, 웹 스토리지를 사용할 경우 데이터가 한 번 저장되면 세션 주기 내에 또는 만료 없이 로컬 스토리지에서 얼마든 불러 쓸 수 있어 보안상 더 안전함
  • 또한 4kb 이내의 제한인 쿠키 대비 훨씬 많은 5MB의 용량 제한으로, 사용자 데이터를 저장할 수 있음
  • 원하는 때에 필요한 데이터만 조회하여 쓸 수 있어 불필요한 요청이 줄고 페이지 로딩이 빨라지며 네트워크 대역폭도 절감할 수 있음
  • 웹 스토리지는 웹 사이트의 중요한 리소스를 저장하는데 사용할 수 있음
  • 렌더링에 있어 중요한 CSS, 자바스크립트, 폰트 파일 등을 저장해놓으면 나중에 사용자가 재방문할 때 로딩 속도를 대폭 개선할 수 있음

💭 Insights

POST 요청이 GET 요청보다 보안적 측면에서 상대적으로 안전하다

  • 개발자 도구의 네트워크 탭을 보면 POST 요청의 쿼리 스트링을 볼 수 있다
  • URL에서만 감춰졌을 뿐 아예 감춰진 것이 아닌데 이정도만으로도 보안적으로 더 나아졌다고 볼 수 있을까?
  • URL은 브라우저 히스토리나 서버 로그에 남을 수 있다.
  • 따라서 URL에 정보가 있으면 다른 사람에게 보여질 가능성이 높다.
  • 또한 리퍼러(referrer) 헤더에 URL이 담기므로 POST가 보안적으로 완전히 안전한 것은 아니더라도 중요한 정보를 URL에 담는 것은 반드시 피해야 한다.
  • 리퍼러 헤더는 링크를 클릭 했을 때 이전 페이지의 URL을 포함한다.
  • 따라서 웹 사이트 소유자는 이 정보를 통해 방문자가 어떤 링크에서 왔는지 알 수 있다.

쿠키와 로컬 스토리지의 보안

  • 책에서는 쿠키에 사용자 정보를 추가하면 매번 네트워크를 통해 전달되어 보안에 취약하다고 한다.
  • 그래서 로컬 스토리지가 보안상 더 안전하다고 하는데, 로컬 스토리지가 쿠키보다 보안적으로 안전한게 맞을까?


  • 일단 쿠키는 CSRF(Cross-Site Request Forgery) 공격에 취약하다.
  • CSRF란 로그인이 된 상태에서 쿠키에 사용자 정보가 있을 때, 악성 링크를 접속하게 하면 이 링크에서 쿠키에 있는 사용자 정보를 이용해 정상 사용자인척 악성 요청을 보내는 것이다.
  • 악성 링크에 접속할 때도 쿠키가 전달되기 때문에 발생하는 문제이다.


  • 로컬 스토리지는 매번 네트워크를 통해 전달되는 것이 아니기 떄문에 CSRF 공격에는 안전하다.
  • 하지만 XSS 공격을 받을 수 있다.
  • XSS란 공격자가 악성 스크립트 파일을 삽입해 사용자는 삽입된 코드를 실행하게 해서 의도치 않은 행동을 수행시키는 공격이다.
  • 이를 통해 로컬 스토리지에 정보가 노출 될 수 있다.
  • 쿠키 또한 XSS 공격에 노출될 수 있지만 httpOnly 옵션이 설정되면 자바스크립트에서 접근이 불가능하게 된다.


  • 책에서는 로컬 스토리지를 이용하는 것이 보안상 더 안전하다고 하지만, XSS 공격을 생각하면 로컬 스토리지 또한 안전하지 않다.
  • 따라서 웹 성능 측면에서는 로컬 스토리지를 사용하는 것이 나을 수 있지만,
  • 보안적으로 더 안전한 것은 없고 둘 다 보안에 신경써서 잘 사용해야 한다고 생각한다.


  • JWT를 이용한 로그인 인증/인가 구현때도 이에 대해 많이 고민하고 찾아봤다.
  • access token과 refresh token을 쿠키에 담거나, 로컬 스토리지에 담는 것 모두 문제점이 있어 선택을 해야 했다.
  • 이 때 refresh token을 쿠키에 저장하고 access token을 로컬 스토리지에 저장한다면,
  • CSRF에서 쿠키가 전달되어도 refresh token만 전달되므로 비교적 안전해진다.
  • 또한 XSS 공격으로 access token이 탈취된다 해도, access token은 만료 기간이 짧기 때문에 비교적 안전해진다.
  • 따라서 refresh token은 쿠키에, access token은 로컬 스토리지에 저장하는 방법을 선택했다.

results matching ""

    No results matching ""